//
//  ForecastView.swift
//  SwiftWeather
//
//  Created by shen on 2023/8/28.
//

import UIKit
import Combine

enum WeatherInfoSegment: Int, CaseIterable {
    case details
    case hourly
    case forecast

    var title: String {
        switch self {
        case .details:
            return "Details"
        case .hourly:
            return "Hourly"
        case .forecast:
            return "3-Day"
        }
    }
}

enum WeatherDetails: String, CaseIterable {
    case wind = "WIND"
    case humidity = "HUMIDITY"
    case pressure = "PRESSURE"
    case visibility = "VISIBILITY"
    case uvIndex = "UV INDEX"

    var icon: UIImage {
        switch self {
        case .wind:
            return Resources.Images.Weather.wind
        case .humidity:
            return Resources.Images.Weather.drop
        case .pressure:
            return Resources.Images.Weather.arrowDownUp
        case .visibility:
            return Resources.Images.Weather.eye
        case .uvIndex:
            return Resources.Images.Weather.sun
        }
    }
}

final class ForecastViewViewModel {
    // MARK: Properties

    @Published private var weather: Weather?

    var numberOfItems: Int {
        WeatherInfoSegment.allCases.count
    }

    var segmentsTitles: [String] {
        WeatherInfoSegment.allCases.map { $0.title }
    }

    var segmentSelectionSubject: CurrentValueSubject<Int, Never> = CurrentValueSubject(0)

    var weatherInfoSelectionPublisher: AnyPublisher<WeatherInfoSegment, Never> {
        weatherInfoChangingSubject.eraseToAnyPublisher()
    }

    var weatherUpdatesPublisher: AnyPublisher<Void, Never> {
        $weather
            .map { _ in }
            .eraseToAnyPublisher()
    }

    private let weatherInfoChangingSubject: PassthroughSubject<WeatherInfoSegment, Never> = PassthroughSubject()

    private let errorSubject: PassthroughSubject<Error, Never> = PassthroughSubject()

    private var cancellables = Set<AnyCancellable>()

    private var currentWeatherSegment: WeatherInfoSegment = .details
    private let temperatureUnits: TemperatureUnits = .celsius
    private let weatherDetails = WeatherDetails.allCases

    // MARK: - Initialization

    init(weather: Weather?) {
        self.weather = weather
        setBindings()
    }

    // MARK: - Methods

    func weatherSegmentSelected(at index: Int) {
        guard let weatherSegment = WeatherInfoSegment(rawValue: index) else { return }
        currentWeatherSegment = weatherSegment
    }

    func viewModelForCurrentWeather() -> CurrentWeatherViewModel {
        return CurrentWeatherViewModel(
            weather: weather?.current,
            location: weather?.location,
            temperatureUnits: temperatureUnits
        )
    }

    func viewModelForWeatherDetailsCell() -> WeatherDetailsCollectionViewCellViewModel {
        return WeatherDetailsCollectionViewCellViewModel(
            weather: weather?.current,
            day: weather?.forecast?.forecastday?.first?.day,
            temperatureUnits: temperatureUnits
        )
    }

    func viewModelForHourlyForecastCell() -> HourlyForecastCollectionViewCellViewModel {
        return HourlyForecastCollectionViewCellViewModel(
            hours: weather?.forecast?.forecastday?.first?.hour,
            temperatureUnits: temperatureUnits
        )
    }

    func viewModelForDailyForecastCell() -> DailyForecastCollectionViewCellViewModel {
        return DailyForecastCollectionViewCellViewModel(
            forecastDays: weather?.forecast?.forecastday,
            temperatureUnits: temperatureUnits
        )
    }

    func weatherSegmentForCell(at indexPath: IndexPath) -> WeatherInfoSegment {
        guard let weatherSegment = WeatherInfoSegment(rawValue: indexPath.item) else {
            fatalError("The number of elements doesn't correspond to the number of segments")
        }
        return weatherSegment
    }
}

// MARK: - Private methods

private extension ForecastViewViewModel {
    func setBindings() {
        segmentSelectionSubject
            .compactMap { WeatherInfoSegment(rawValue: $0) }
            .sink { [weak self] segment in
                self?.currentWeatherSegment = segment
                self?.weatherInfoChangingSubject.send(segment)
            }
            .store(in: &cancellables)
    }
}

class ForecastView: BaseView, ViewModelable {
    typealias ViewModel = ForecastViewViewModel

    var viewModel: ViewModel! {
        didSet {
            configureWeatherSegments()
            setBindings()
        }
    }

    private var cancellables = Set<AnyCancellable>()

    // MARK: - Views

    private lazy var currentWeatherView = CurrentWeatherView()

    private lazy var weatherInfoSegmentedControl: UISegmentedControl = {
        let segmentedControl = UISegmentedControl()
        segmentedControl.selectedSegmentTintColor = .systemBlue

        segmentedControl.setTitleTextAttributes([
            .foregroundColor: UIColor.systemBlue,
            .font: Resources.Fonts.system()
        ], for: .normal)

        segmentedControl.setTitleTextAttributes([
            .foregroundColor: Resources.Colors.background,
            .font: Resources.Fonts.system(weight: .medium)
        ], for: .selected)

        segmentedControl.addTarget(self, action: #selector(weatherSegmentDidChange), for: .valueChanged)

        return segmentedControl
    }()

    private lazy var weatherInfoCollectionView: UICollectionView = {
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout())

        collectionView.sf.register(cellWithClass: WeatherDetailsCollectionViewCell.self)
        collectionView.sf.register(cellWithClass: HourlyForecastCollectionViewCell.self)
        collectionView.sf.register(cellWithClass: DailyForecastCollectionViewCell.self)

        collectionView.showsHorizontalScrollIndicator = false
        collectionView.allowsSelection = false
        collectionView.backgroundColor = Resources.Colors.secondaryBackground
        return collectionView
    }()

    private lazy var weatherDetailsVStack = UIStackView(
        arrangedSubviews: [
            weatherInfoSegmentedControl
                .padded(insets: .init(top: 0, left: 20, bottom: 0, right: 20)),
            weatherInfoCollectionView
        ],
        axis: .vertical,
        spacing: 20
    )

    private lazy var mainVStack = UIStackView(
        arrangedSubviews: [
            currentWeatherView
                .padded(insets: .init(top: 0, left: 20, bottom: 0, right: 20)),
            weatherDetailsVStack
        ],
        axis: .vertical,
        spacing: 40
    )

    // MARK: - Methods

    override func setupViews() {
        weatherInfoCollectionView.dataSource = self
        weatherInfoCollectionView.delegate = self

        addSubview(mainVStack, useAutoLayout: true)
    }

    override func constraintViews() {
        NSLayoutConstraint.activate([
            mainVStack.topAnchor.constraint(equalTo: topAnchor, constant: 20),
            mainVStack.leadingAnchor.constraint(equalTo: leadingAnchor),
            mainVStack.trailingAnchor.constraint(equalTo: trailingAnchor),
            mainVStack.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }

    var scrollingBySegment = false
}

// MARK: - Actions

@objc private extension ForecastView {
    func weatherSegmentDidChange() {
        let seletedIndex = weatherInfoSegmentedControl.selectedSegmentIndex
        viewModel.segmentSelectionSubject.send(seletedIndex)
    }
}

// MARK: - Private methods

private extension ForecastView {
    func setBindings() {
        viewModel.weatherInfoSelectionPublisher
            .sink { [weak self] segment in
                self?.weatherInfoCollectionView.scrollToItem(
                    at: IndexPath(item: segment.rawValue, section: 0),
                    at: .centeredHorizontally,
                    animated: true
                )
            }
            .store(in: &cancellables)

        viewModel.weatherUpdatesPublisher
            .sink { [weak self] in
                self?.updateUI()
            }
            .store(in: &cancellables)
    }

    func updateUI() {
        currentWeatherView.viewModel = viewModel.viewModelForCurrentWeather()
        weatherInfoCollectionView.reloadData()
    }

    func createCollectionViewLayout() -> UICollectionViewCompositionalLayout {
        let item = NSCollectionLayoutItem(
            layoutSize: .init(
                widthDimension: .fractionalWidth(1),
                heightDimension: .fractionalHeight(1)
            )
        )

        let group = NSCollectionLayoutGroup.horizontal(
            layoutSize: .init(
                widthDimension: .fractionalWidth(1),
                heightDimension: .fractionalHeight(1)
            ),
            subitems: [item]
        )

        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging
        section.visibleItemsInvalidationHandler = { [weak self] visibleItems, _, _ in
            guard let index = visibleItems.last?.indexPath.row else { return }

            self?.weatherInfoSegmentedControl.selectedSegmentIndex = index
            self?.viewModel.weatherSegmentSelected(at: index)
        }

        return UICollectionViewCompositionalLayout(section: section)
    }

    func configureWeatherSegments() {
        weatherInfoSegmentedControl.removeAllSegments()

        viewModel.segmentsTitles.enumerated().forEach { index, segmentTitle in
            weatherInfoSegmentedControl.insertSegment(withTitle: segmentTitle, at: index, animated: false)
        }

        weatherInfoSegmentedControl.selectedSegmentIndex = viewModel.segmentSelectionSubject.value
    }
}

// MARK: - UICollectionViewDataSource & UICollectionViewDelegate

extension ForecastView: UICollectionViewDataSource, UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        viewModel != nil ? viewModel.numberOfItems : 0
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let weatherSegment = viewModel.weatherSegmentForCell(at: indexPath)

        switch weatherSegment {
        case .details:
            let cell = collectionView.sf.dequeueReusableCell(withClass: WeatherDetailsCollectionViewCell.self, for: indexPath)
            cell.viewModel = viewModel.viewModelForWeatherDetailsCell()
            return cell
        case .hourly:
            let cell = collectionView.sf.dequeueReusableCell(withClass: HourlyForecastCollectionViewCell.self, for: indexPath)
            cell.viewModel = viewModel.viewModelForHourlyForecastCell()
            return cell
        case .forecast:
            let cell = collectionView.sf.dequeueReusableCell(withClass: DailyForecastCollectionViewCell.self, for: indexPath)
            cell.viewModel = viewModel.viewModelForDailyForecastCell()
            return cell
        }
    }
}
